#pragma once

#include"Common.hpp"
#include"Log.hpp"
#include"Mutex.hpp"
#include"InetAddr.hpp"    
#include<sys/wait.h>   //防止僵尸进程  wait头文件
#include<signal.h>     //信号头文件
#include<pthread.h>    //线程
#include<functional>
#include"ThreadPool.hpp"

using namespace LogModule;
using namespace MutexModule;
using namespace ThreadPoolModule;

// using task_t = std::function<void()>;

//想让上层处理对应的服务  网络部分只需要带出去
//今天的回调方法：返回值是void   参数是 1.文件描述符（整数）  2.addr 拷贝出来（不受引用影响）
//获得一个新链接：文件描述符是谁，链接是谁连接的   
// using func_t = std::function<void(int,InetAddr addr)>;  //怎么处理请求不是tcpserver关心的
 
//简单些  把返回值修改
using func_t = std::function<std::string(const std::string &word,InetAddr &addr)>;    //把要输的单词  

//表示默认的缺省的文件描述符值
const static int defaultsockfd = -1;
const static int backlog = 8;

//注意：服务器一般都是禁止拷贝的
//未来UDP不想被拷贝   也继承一下这个类
class TcpServer:public NoCopy   //继承NoCopy 这个类   （想要形成TcpServer的拷贝，就要先把基类拷贝）  
{
public:
    TcpServer(uint16_t port,func_t func)
        : _port(port),
        _listensockfd(),
        _isrunning(false),
        _func(func)
    {}

    void Init()
    {
        //防止串行运行  等待阻塞  使用忽略信号  SIG_IGN   
        signal(SIGCHLD,SIG_IGN);    //忽略 SIGCHLD 信号    { 较为推荐 }


    //1.创建套接字(socket)
        _listensockfd = socket(AF_INET,SOCK_STREAM,0);
        //创建套接字失败(输出日志信息)
        if(_listensockfd < 0)
        {
            //用致命消息来进行打印，输出日志
            LOG(LogLevel::FATAL) << "socket create error";
            exit(SOCKET_ERR);    //用来终止进程    有退出码
        }
        //标准输入   标准输出   标准错误   默认会打开   0 1 2       再打开一个文件描述符默认就是3
        //套接字创建成功  日常日志   打印套接字信息
        LOG(LogLevel::INFO) << "socket create success,sockfd: " << _listensockfd;   


    //2.绑定众所周知的端口号   
        //bind  第一个参数：要绑定的文件描述符   第二个：sockaddr,创建sockaddr_in结构体并填充地址   第三个:长度
        InetAddr local(_port);
        int n = bind(_listensockfd,local.NetAddrPtr(),local.NetAddrLen());
        if(n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "sockfd bind success" << _listensockfd;  


    //3.设置 socket 状态为listen   listen状态  就可以被链接了
        n = listen(_listensockfd,backlog);
        if(n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success";
    }
////////////////////////////////// 前面的套接字都是listensockfd /////////////////////////////////////////////


///// 多线程版本 内部类（结构体对象） /////
    class ThreadData
    {
    public:
    //加一个构造
        ThreadData(int fd,InetAddr &ar,TcpServer *s):sockfd(fd),addr(ar),tsvr(s)
        {}

        int sockfd;
        InetAddr addr;     //InetAddr 在定义的时候不需要构造函数   直接无参构造
        TcpServer *tsvr;   //定义一个指针
    };


    // 服务函数(因为是echoserver   读取数据  写回数据)
    void Service(int sockfd,InetAddr &peer)    //参数把客户给加进来   文件描述符和客户都有了
    {
        char buffer[1024];
        while(true)
        {
            //1.先读取数据
    //对于读取数据的三种情况：
    //a. n > 0   读取成功
    //b. n < 0   读取失败
    //c. n == 0  对端把链接关闭了，读到了文件的结尾 ----> 管道一直读（但是不写会一直读，不写且关闭就会读到0,表明读到文件结尾）
        
        //对于 这里服务器读取的时候 是有bug的（TCP面向字节流，可能只是一部分）
            ssize_t n = read(sockfd,buffer,sizeof(buffer)-1);   //sizeof(buffer)-1 期望读的字节数     n 返回字节数 
        //读成功  返回读的字节数（读多少  返回多少）
            if(n > 0)
            {
            //在这认知  buffer 是一个英文单词   or   一个命令字符串
                buffer[n] = 0;     //设置成为C风格的字符串  n <= sizeof(buffer)-1
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " say# " << buffer;   //读到了用户发的消息
            
            //不再写回  数据   而是直接回调  当前层回调
                std::string echo_string = _func(buffer,peer);     //peer  把客户端带出来   
            //(服务器不关心如何处理，把数据传递给上层，buffer传递出去，当前层实现回调)


            // //2.写回数据
            // std::string echo_string = "echo# ";
            // echo_string += buffer;

            write(sockfd,echo_string.c_str(),echo_string.size());   //往sockfd里写，写echo_string,写大小size()
            }

        //把其他对应的
            else if(n == 0)
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 客户端退出了....";
                close(sockfd);    //关闭文件描述符
                break;
            }

        //小于0  代表出异常了
            else
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << "客户端读取异常了....";
                close(sockfd);
                break;   
            }
        }
    }

//执行方法(Routine 如果是类内方法，就有this指针，无法直接使用   所以需要static修饰)
    static void *Routine(void *args)     //静态方法不能访问类内的属性或者成员  因为有this指针
    {
        pthread_detach(pthread_self());
        
        ThreadData *td = static_cast<ThreadData*>(args);
        td->tsvr->Service(td->sockfd,td->addr);

        delete td;
        return nullptr;
    }

    void Run()
    {
        _isrunning = true;
        while(_isrunning)
        {
            //对于服务器 --> 需要获取对应的链接
            //a.获取新链接     accept
            struct sockaddr_in peer;
            socklen_t len = sizeof(sockaddr_in);   //应该是结构体的大小
            //如果没有连接来   就阻塞
            int sockfd = accept(_listensockfd,CONV(peer),&len);   //CONV 强转成为struct sockaddr*   长度就是&len
            //获取失败
            if(sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }
            //获取成功
            InetAddr addr(peer);    //传peer  调用重载的构造函数
            LOG(LogLevel::INFO) << "accept success,peer addr : " << addr.StringAddr();   //获取对应新链接
    
        // 多线程不适合常任务   翻译和命令执行都是常任务   使用多线程模式
            ThreadData *td = new ThreadData(sockfd,addr,this);     //构造  线程数据
            pthread_t tid;

        //线程受理之后就会进入Routine    Routines之后就会Service,Service会通过网络读，读完直接翻译
            pthread_create(&tid,nullptr,Routine,td);    //让线程执行特定的任务Routine     线程传递的参数td        
        }
        _isrunning = false;
    }

    ~TcpServer()
    {}

private:
    uint16_t _port;    
    int _listensockfd;    //套接字   监听sockfd   套接字分为listensockfd  也分为accept套接字（IO）

    //为了让服务器是死循环
    bool _isrunning;      //标志位

    //设置回调   这样构建服务器的时候传进一个回调函数参数
    func_t _func;         
};
